// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/gdax/api_order/fix/fix_trade_client.h"

#include <utility>

#include "coin2/base/base64.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/gdax/api_order/fix/fix_fields.h"
#include "quickfix/Application.h"
#include "quickfix/Session.h"
#include "quickfix/SessionSettings.h"

namespace coin2::exchange::gdax::api_order::fix {

SignatureGenerator::SignatureGenerator(const FIX::Message& message) {
  Parse(message);
  Validate();
}

void SignatureGenerator::Parse(const FIX::Message& message) {
  const FIX::Header& header = message.getHeader();
  header.getFieldIfSet(msg_type);
  header.getFieldIfSet(msg_seq_num);
  header.getFieldIfSet(sending_time);
  header.getFieldIfSet(begin_string);
  header.getFieldIfSet(sender_comp_id);
  header.getFieldIfSet(target_comp_id);
  message.getFieldIfSet(password);
}

void SignatureGenerator::Validate() const {
  // TODO(daniel) - validate
}

std::string SignatureGenerator::Apply(std::string_view secret_key) const {
  std::stringstream ss;
  ss << sending_time.getString() << delimSOH << msg_type.getString() << delimSOH
     << msg_seq_num.getString() << delimSOH << sender_comp_id.getString() << delimSOH
     << target_comp_id.getString() << delimSOH << password.getString();
  using SignGenerator = impl::ApiSignatureUtil;
  return SignGenerator::GetSignatureByHmacSHA256(ss.str(), impl::base64_decode(secret_key.data()));
}

FixTradeClient::FixTradeClient(std::string_view auth_key_file)
    : FixTradeClientBase<FixTradeClient>(auth_key_file) {
  SetSessionSetting(trade_client_config_);
  msg_builder_ = std::make_unique<FixMessageBuilder>();
}

void FixTradeClient::onCreate(const FIX::SessionID&) {
  // do nothing
}

void FixTradeClient::onLogon(const FIX::SessionID&) { IsLogon(true); }

void FixTradeClient::onLogout(const FIX::SessionID& session_id) { IsLogon(false); }

void FixTradeClient::toAdmin(FIX::Message& message, const FIX::SessionID& session_id) {
  FIX::MsgType msg_type;
  message.getHeader().getFieldIfSet(msg_type);
  if (msg_type == "A") {
    FillLogonMessage(message);
  }
}

void FixTradeClient::toApp(FIX::Message&, const FIX::SessionID&) EXCEPT(FIX::DoNotSend) {
  // do nothing
}

void FixTradeClient::fromAdmin(const FIX::Message& message, const FIX::SessionID& session_id)
    EXCEPT(FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon) {
  // ProcessIncomeMsg(message, session_id);
}
void FixTradeClient::fromApp(const FIX::Message& message, const FIX::SessionID& session_id) EXCEPT(
    FIX::FieldNotFound,
    FIX::IncorrectDataFormat,
    FIX::IncorrectTagValue,
    FIX::UnsupportedMessageType) {
  ProcessIncomeMsg(message, session_id);
}

void FixTradeClient::FillLogonMessage(FIX::Message& message) {
  try {
    // 1. get setting
    FIX::SessionID session_id = message.getSessionID();
    const FIX::Dictionary* settings = getInitiator().getSessionSettings(session_id);
    if (settings == nullptr) {
      return;
    }
    std::string password;
    if (settings->has(PASSWORD)) {
      password = settings->getString(PASSWORD);
    }
    if (password.empty()) {
      LOG(ERROR) << "password is not found";
    }
    message.setField(FIX::Password(password));
    DLOG(INFO) << "******" << message;
    std::string secret_key;
    if (settings->has(SECRET_KEY)) {
      secret_key = settings->getString(SECRET_KEY);
    }
    std::string cood;
    if (settings->has(CANCEL_ORDERS_ON_DISCONNECT)) {
      cood = settings->getString(CANCEL_ORDERS_ON_DISCONNECT);
    }
    // 2. generate signature
    SignatureGenerator sign_generator(message);
    auto signature = sign_generator.Apply(secret_key);
    // 3. set fields
    message.setField(FIX::RawData(signature));
    message.setField(FIX::CancelOrdersOnDisconnect(cood));
    if (settings->has(DROP_COPY_FLAG)) {
      std::string drop_copy_flag = settings->getString(DROP_COPY_FLAG);
      message.setField(FIX::DropCopyFlag(drop_copy_flag));
    }
  } catch (FIX::Exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  } catch (std::exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  }
}

void FixTradeClient::Start() { FixTradeClientBase<FixTradeClient>::StartBase(); }

void FixTradeClient::Stop() { FixTradeClientBase<FixTradeClient>::StopBase(); }

bool FixTradeClient::IsAvailable() const {
  return FixTradeClientBase<FixTradeClient>::IsAvailable();
}

void FixTradeClient::SetExecutor(Executor* executor) {
  FixTradeClientBase<FixTradeClient>::SetExecutor(executor);
}

void FixTradeClient::SetProcessor(Processor* processor) {
  FixTradeClientBase<FixTradeClient>::SetProcessor(processor);
}

void FixTradeClient::AddSubscriber(SubscriberCB cb) {
  FixTradeClientBase<FixTradeClient>::AddSubscriberV2(cb);
}

void FixTradeClient::PlaceOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GeneratePlaceOrder(ord_exec, GetProductHolder());
  if (!message) {
    LOG(ERROR) << "failed to generate place order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::CancelOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GenerateCancelOrder(ord_exec);
  if (!message) {
    LOG(ERROR) << "failed to generate cancel order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::QueryOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GenerateQueryOrder(ord_exec);
  if (!message) {
    LOG(ERROR) << "failed to generate query order: " << ord_exec.proc_order_id;
    return;
  }
  // this->Send(std::move(message));
}

void FixTradeClient::AssignSubSystemAuthKey(FIX::SessionSettings* session_setting) {
  auto auth_key = AuthKey::FromFile(GetAuthKey());
  ValidateSubSystemAuthKey(auth_key);
  std::string sender_comp_id = auth_key.access_key();
  FIX::Dictionary dict;
  dict.setString(SECRET_KEY, auth_key.secret_key());
  if (auto passphrase = auth_key.GetValue(PASSPHRASE); passphrase) {
    dict.setString(PASSWORD, *passphrase);
  }
  FIX::SessionID session_id{begin_string_, sender_comp_id, target_comp_id_};
  session_setting->set(session_id, dict);
}

void FixTradeClient::ValidateSubSystemAuthKey(const AuthKey& auth_key) const {
  // TODO(daniel) - validate
}

}  // namespace coin2::exchange::gdax::api_order::fix
